/*
 * Copyright 2007 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.jtell.internal.config.xml;

import org.jtell.internal.Constants;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.LinkedList;

/**
 * <p>
 * <code>ConfigurationContentHandler</code> is an implementation of the {@link ContentHandler} which is used to parse
 * <code>jtell.xml</code> configuration files.
 * </p>
 * <p>
 * <strong>Thread Safety</strong><br/>
 * Instances of this class are not safe for multithreaded access.
 * </p>
 */
public class ConfigurationContentHandler extends DefaultHandler
{
    /**
     * <p>
     * Parse context stack.
     * </p>
     */
    private final LinkedList<Object> m_contextStack = new LinkedList<Object>();

    /**
     * <p>
     * The root element, after parse is complete.
     * </p>
     */
    private JTellElement m_rootElement = null;

    /**
     * <p>
     * Construct a {@link ConfigurationContentHandler} instance.
     * </p>
     */
    public ConfigurationContentHandler()
    {
        super();
    }

    /* inherit javadoc */
    public void startElement(final String uri,
                             final String localName,
                             final String qName,
                             final Attributes attributes) throws SAXException
    {
        if (Constants.JTELL_NAMESPACE.equals(uri))
        {
            if ("jtell".equals(qName))
            {
                m_rootElement = null;
                pushContext(new JTellElement());
            }
            else if ("listener".equals(qName))
            {
                pushContext(new ListenerElement()).setListenerClassName(attributes.getValue("class"));
            }
            else if ("handler".equals(qName))
            {
                pushContext(new HandlerElement()).setMethodSignature(attributes.getValue("method"));
            }
            else if ("event".equals(qName))
            {
                pushContext(new EventElement()).setEventClassName(attributes.getValue("class"))
                        .setSourceClassName(attributes.getValue("source-class"));
            }
            else if ("attribute".equals(qName))
            {
                peekContext(EventElement.class).addAttribute(attributes.getValue("name"), attributes.getValue("value"));
            }
            else if ("after".equals(qName))
            {
                peekContext(HandlerElement.class).addAfterOrder(attributes.getValue("token"),
                        Boolean.parseBoolean(attributes.getValue("required")));
            }
            else if ("before".equals(qName))
            {
                peekContext(HandlerElement.class).addBeforeOrder(attributes.getValue("token"),
                        Boolean.parseBoolean(attributes.getValue("required")));
            }
            else if ("contribute".equals(qName))
            {
                peekContext(HandlerElement.class).addContributeToken(attributes.getValue("token"));
            }
        }
    }

    /* inherit javadoc */
    public void endElement(final String uri, final String localName, final String qName) throws SAXException
    {
        if (Constants.JTELL_NAMESPACE.equals(uri))
        {
            if ("jtell".equals(qName))
            {
                m_rootElement = popContext(JTellElement.class);
            }
            else if ("listener".equals(qName))
            {
                final ListenerElement element = popContext(ListenerElement.class);
                peekContext(JTellElement.class).addListener(element);
            }
            else if ("handler".equals(qName))
            {
                final HandlerElement element = popContext(HandlerElement.class);
                peekContext(ListenerElement.class).addHandler(element);
            }
            else if ("event".equals(qName))
            {
                final EventElement element = popContext(EventElement.class);
                peekContext(HandlerElement.class).addEvent(element);
            }
        }
    }

    /**
     * <p>
     * Get the root configuration element through which the data may be read. Valid only after parsing is complete.
     * </p>
     *
     * @return {@link JTellElement} instance.
     * @throws IllegalStateException if a parse operation has not completed.
     */
    public JTellElement getRootElement() throws IllegalStateException
    {
        if (null == m_rootElement)
        {
            throw new IllegalStateException("Parsing is not complete.");
        }
        return m_rootElement;
    }

    /**
     * <p>
     * Get the topmost element from the parser context stack.
     * </p>
     *
     * @param clazz the element type.
     * @return {@link Object} element.
     */
    @SuppressWarnings("unchecked")
    private <T> T peekContext(final Class<T> clazz)
    {
        return (T) m_contextStack.getLast();
    }

    /**
     * <p>
     * Remove the topmost element from the parser context stack.
     * </p>
     *
     * @param clazz the element type.
     * @return {@link Object} element.
     */
    @SuppressWarnings("unchecked")
    private <T> T popContext(final Class<T> clazz)
    {
        return (T) m_contextStack.removeLast();
    }

    /**
     * <p>
     * Add an element to the top of the parser context stack.
     * </p>
     *
     * @param element the element to add.
     * @return {@link Object} pushed object.
     */
    private <T> T pushContext(final T element)
    {
        m_contextStack.add(element);
        return element;
    }
}
